package gu.sql2java.manager;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.nullToEmpty;
import static gu.sql2java.SimpleLog.log;
import static com.google.common.base.MoreObjects.firstNonNull;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import gu.sql2java.BaseBean;
import gu.sql2java.BaseRow;
import gu.sql2java.RowMetaData;
import gu.sql2java.TableManager;
import gu.sql2java.TableManager.Action;
import gu.sql2java.manager.cache.CacheManager;


/**
 * {@link TableManager}工具类
 * @author guyadong
 * @since 3.15.4
 */
public class TableManagers {
	private static final int CACHE_MAXIMUMPOOLSIZE = Runtime.getRuntime().availableProcessors();
	private static final long CACHE_KEEPALIVETIME = 60L;
	private static final int CACHE_QUEUECAPACITY = 1024;
	private static final String CACHE_NAMEFORMAT = "sql2java-pool-%d";
	private static Boolean debug;
	/** 全局线程池(自动退出) */
	private static class Singleton{
		static final ExecutorService GLOBAL_EXECUTOR = createCachedPool();
	}
    /** 创建通用任务线程池对象 */
    private static final ExecutorService createCachedPool(){
        ExecutorService executor = MoreExecutors.getExitingExecutorService(
                new ThreadPoolExecutor(
                        CACHE_MAXIMUMPOOLSIZE, 
                        CACHE_MAXIMUMPOOLSIZE,
                        CACHE_KEEPALIVETIME,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(CACHE_QUEUECAPACITY),
                        new ThreadFactoryBuilder()
                            .setNameFormat(CACHE_NAMEFORMAT)
                            .build())
                );
        return executor;
    }
    private static Boolean isDebug() {
        return null == debug ? BaseTableManager.isDebug() : debug;
    }
    public static void setDebug(Boolean debug) {
        TableManagers.debug = debug;
    }
    /**
	 * 返回线程池对象
	 */
	public static ExecutorService getExecutorService(){
		return Singleton.GLOBAL_EXECUTOR;
	}
	/**
     * 基于{@link QueueProducerAction}异步执行{@link TableManager#loadByJoinWhereForAction(String, String, Object[], int[], int, int, gu.sql2java.TableManager.Action)},
     * 查询的数据记录输出到阻塞队列{@link BlockingQueue}
     * 
     * @param manager
	 * @param join JOIN查询参数
	 * @param where WHERE查询参数
	 * @param argList the arguments to use fill given prepared statement,may be null
	 * @param fieldList table of the field's associated constants
	 * @param numRows 每次查询返回的记录条数
	 * @param action
	 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时结束循环
	 * @param executor 异步执行对象,为{@code null}则使用默认线程池对象{@link TableManagers.Singleton#GLOBAL_EXECUTOR}
	 * @see BaseTableManager#loadByJoinWhereForAction(String, String, Object[], int[], int, int, gu.sql2java.TableManager.Action)
     */
    public static <B extends BaseBean>
    void loadByJoinWhereAsync(TableManager<B>manager,String join,String where,Object[] argList, int[] fieldList, int numRows, TableManager.Action<B> action, AtomicBoolean stopped, Executor executor){
    	checkArgument(null != manager,"manager is null");
    	checkArgument(null != action,"action is null");
    	if(0 == numRows){
    	    return ;
    	}
    	firstNonNull(executor,Singleton.GLOBAL_EXECUTOR).execute(()->{
    	    if(numRows < 0){
    	        if(null != stopped && stopped.get()){
    	            return;
    	        }
    	        log(isDebug(),"loadByJoinWhereAsync:join={} where={}\nargs [{}]",join,where,
    	                null == argList? "[]":Joiner.on(",").join(Iterables.transform(Arrays.asList(argList), String::valueOf)));
    	        manager.loadByJoinWhereForAction(join, where, argList, fieldList , 1, -1, action);
    	    }else {
    	        int startRow = 1;
    	        int count = 0;
    	        do{
    	            if(null != stopped && stopped.get()){
    	                break;
    	            }
    	            startRow += count;
                    log(isDebug(),"loadByJoinWhereAsync:join={} where={},numRows={}\nargs [{}]",join,where,numRows,
                            null == argList? "[]":Joiner.on(",").join(Iterables.transform(Arrays.asList(argList), String::valueOf)));
    	            count = manager.loadByJoinWhereForAction(join, where, argList, fieldList, startRow, numRows, action);
    	        }while(count == numRows);
            }
    	});
    }
    /**
     * 基于{@link QueueProducerAction}异步执行
     * {@link TableManager#loadByJoinWhereForAction(String, String, Object[], int[], int, int, gu.sql2java.TableManager.Action)},
     * 查询的数据记录输出到阻塞队列{@link BlockingQueue},
     * 与{@link #loadByJoinWhereAsync(TableManager, String, String, Object[], int[], int, int, int, AtomicBoolean, Function, Executor)}不同是阻塞队列由调用层提供，
     * 基于此创建{@link QueueProducerAction}实例
     * @see #loadByJoinWhereAsync(TableManager, String, String, Object[], int[], int, gu.sql2java.TableManager.Action, AtomicBoolean, Executor)
     * @param manager
     * @param join 查询参数
     * @param where 查询参数
     * @param argList the arguments to use fill given prepared statement,may be null
     * @param fieldList table of the field's associated constants
     * @param numRows 每次查询返回的记录条数
     * @param queue {@link QueueProducerAction}构造方法参数，参见 {@link QueueProducerAction#QueueProducerAction(int, int, Function)}
     * @param queueTimeout {@link QueueProducerAction}构造方法参数，参见 {@link QueueProducerAction#QueueProducerAction(int, int, Function)}
     * @param stopped 停止标志,为{@code null}忽略,为{@code true}时结束循环
     * @param transformer {@link QueueProducerAction}构造方法参数，参见 {@link QueueProducerAction#QueueProducerAction(int, int, Function)}
     * @param executor 异步执行对象,为{@code null}则使用默认线程池对象{@link TableManagers.Singleton#GLOBAL_EXECUTOR}
     */
    public static <B extends BaseBean,T>
    void loadByJoinWhereAsync(TableManager<B>manager,String join,
    		String where,Object[] argList,int[] fieldList,int numRows, BlockingQueue<T>queue, int queueTimeout, AtomicBoolean stopped, Function<B, T>transformer, Executor executor){
    	QueueProducerAction<B, T> action = new QueueProducerAction<>(queue, queueTimeout, transformer);
    	loadByJoinWhereAsync(manager,join,where,argList, fieldList, numRows, action, stopped, executor);
    }
    /**
     * 基于{@link QueueProducerAction}异步执行{@link TableManager#loadByJoinWhereForAction(String, String, Object[], int[], int, int, gu.sql2java.TableManager.Action)},
     * 查询的数据记录输出到阻塞队列{@link BlockingQueue},
     * 与{@link #loadByJoinWhereAsync(TableManager, String, String, Object[], int[], int, BlockingQueue, int, AtomicBoolean, Function, Executor)}不同的是
     * 阻塞队列{@link BlockingQueue}根据输入参数创建
     * @see #loadByJoinWhereAsync(TableManager, String, String, Object[], int[], int, BlockingQueue, int, AtomicBoolean, Function, Executor)
     * @param manager
     * @param join 查询参数
     * @param where 查询参数
     * @param argList the arguments to use fill given prepared statement,may be null
     * @param fieldList table of the field's associated constants
     * @param numRows 每次查询返回的记录条数
     * @param queueCapacity {@link QueueProducerAction}构造方法参数，参见 {@link QueueProducerAction#QueueProducerAction(int, int, Function)}
     * @param queueTimeout {@link QueueProducerAction}构造方法参数，参见 {@link QueueProducerAction#QueueProducerAction(int, int, Function)}
     * @param stopped 停止标志,为{@code null}忽略,为{@code true}时结束循环
     * @param transformer {@link QueueProducerAction}构造方法参数，参见 {@link QueueProducerAction#QueueProducerAction(int, int, Function)}
     * @param executor 异步执行对象,为{@code null}则使用默认线程池对象{@link TableManagers.Singleton#GLOBAL_EXECUTOR}
     * @return 创建的阻塞队列
     */
    public static <B extends BaseBean,T>
    BlockingQueue<T> loadByJoinWhereAsync(TableManager<B>manager,String join,
    		String where, Object[] argList,int[] fieldList,int numRows, int queueCapacity, int queueTimeout, AtomicBoolean stopped, Function<B, T>transformer, Executor executor){
    	BlockingQueue<T>queue = new LinkedBlockingQueue<T>(queueCapacity);
    	loadByJoinWhereAsync(manager,join,where,argList,fieldList,numRows, queue, queueTimeout, stopped, transformer, executor);
    	return queue;
    }
    /**
     * {@link #loadByJoinWhereAsync(TableManager, String, String, Object[], int[], int, BlockingQueue, int, AtomicBoolean, Function, Executor)}的简化版，
     * 返回队列类型为原始数据库对象类型
     * @see #loadByJoinWhereAsync(TableManager, String, String, Object[], int[], int, BlockingQueue, int, AtomicBoolean, Function, Executor)
     */
    public static <B extends BaseBean>
    BlockingQueue<B> loadByJoinWhereAsync(TableManager<B>manager,String join,
    		String where, Object[] argList,int numRows, int queueCapacity, int queueTimeout, AtomicBoolean stopped, Executor executor){
    	return loadByJoinWhereAsync(manager,join,where,argList,null,numRows, queueCapacity, queueTimeout, stopped, b->b, executor);
    }
    /**
     * 聚合(统计)查询<br>
     * @param manager
     * @param columns SELECT 语句字段输出表达式列表部分
     * @param where SELECT 语句 WHERE条件e表达式部分,为{@code null}忽略
     * @param groupBy 聚合查询字段名列表
     * @param having HAVING 表达式,为{@code null}忽略
     * @param orderBy 排序表达式(ORDER BY起始),为{@code null}忽略
     * @param argList SQL语句中的参数列表
     * @return 聚合查询结果
     */
    public static <B extends BaseBean> List<BaseBean> aggregateAsList(
    		TableManager<B>manager,
    		String columns,String where,String groupBy,String having,String orderBy, Object... argList){
    	columns = nullToEmpty(columns).trim();
    	where = nullToEmpty(where).trim();
    	groupBy = nullToEmpty(groupBy).trim();
    	having = nullToEmpty(having).trim();
    	orderBy = nullToEmpty(orderBy).trim();
    	checkArgument(!columns.isEmpty(),"columns is null or empty");
        checkArgument(!groupBy.isEmpty(),"groupBy is null or empty");
        where = Pattern.compile("^WHERE",Pattern.CASE_INSENSITIVE).matcher(where).replaceAll("");
        groupBy = Pattern.compile("^GROUP +BY",Pattern.CASE_INSENSITIVE).matcher(groupBy).replaceAll("");
        having = Pattern.compile("^HAVING",Pattern.CASE_INSENSITIVE).matcher(having).replaceAll("");
        orderBy = Pattern.compile("^ORDER +BY",Pattern.CASE_INSENSITIVE).matcher(orderBy).replaceAll("");
        String sql = String.format("SELECT %s FROM %s %s %s %s %s",
        		columns,
        		tablenameOf(manager),
        		where.isEmpty() ? "" : "WHERE "+ where,
        		"GROUP BY " + groupBy,
        		having.isEmpty() ? "" : "HAVING " + having,
				orderBy.isEmpty() ? "" : "ORDER BY " + orderBy);
        return checkNotNull(manager,"manager is null").runSqlAsList(sql,argList);
    }
    private static <B extends BaseBean> String tablenameOf(TableManager<B> manager){
        if(manager instanceof BaseTableManager){
            return  ((BaseTableManager<B>)manager).metaData.tablename;
        }else {
            return manager.createBean().tableName();
        }
    }
	/**
		 * 更新记录主键
		 * @param alias
		 * @param tablename
		 * @param from
		 * @param to
		 * @since 3.30.0
		 */
	public static void updatePk(String alias, String tablename,Object[] from,Object[] to) {
		if(!Arrays.deepEquals(from, to)) {
			checkArgument(Arrays.stream(checkNotNull(from,"from is null")).allMatch(e->null!=e),"argument 'from' has null element");
			checkArgument(Arrays.stream(checkNotNull(to    ,"to is null"     )).allMatch(e->null!=e),"argument 'to' has null element");
			RowMetaData metaData = RowMetaData.getMetaData(tablename);
			checkArgument(metaData.primaryKeyCount==from.length,"%s array length required for 'from'",metaData.primaryKeyCount);
			checkArgument(metaData.primaryKeyCount==to.length,"%s array length required for 'to'",metaData.primaryKeyCount);
			String sql = String.format("UPDATE %s SET %s WHERE %s",
					tablename,
					Arrays.stream(metaData.primaryKeyNames).map(s->s+"=?") .reduce((l,r)->l+","+r).get(),
					Arrays.stream(metaData.primaryKeyNames).map(s->s+"=?") .reduce((l,r)->l+" AND "+r).get());
			Object[] argList = new Object[metaData.primaryKeyCount*2];
			System.arraycopy(from, 0, argList, 0, metaData.primaryKeyCount);
			System.arraycopy(to, 0, argList, metaData.primaryKeyCount, metaData.primaryKeyCount);
			//			log("sql={}",sql);
			Managers.getSqlRunner(alias).runSql(sql,argList);
			CacheManager manager = Managers.getCacheManagerOrNull(tablename);
			if(null != manager) {
				/** 删除对应缓存数据  */
				manager.removeCached(from);
			}
		}
	}
	/**
	 * 更新记录主键(适用只有一个主键的表)
	 * @param alias
	 * @param tablename
	 * @param from
	 * @param to
	 * @since 3.30.0
	 */
	public static void updatePk(String alias, String tablename,Object from,Object to) {
		if(!Objects.equal(from, to)) {
			RowMetaData metaData = RowMetaData.getMetaData(tablename);
			if(metaData.primaryKeyCount != 1) {
				throw new UnsupportedOperationException("ONLY ONE primary key required for "+ tablename);
			}
			Managers.getSqlRunner(alias).runSql(
					"UPDATE ${tablename} SET ${pk}=? WHERE ${pk}=?"
						.replace("${tablename}", tablename)
						.replace("${pk}", metaData.primaryKeyNames[0]),
					new Object[] { checkNotNull(to,"to is null"),checkNotNull(from,"from is null")});
			CacheManager manager = Managers.getCacheManagerOrNull(tablename);
			if(null != manager) {
				/** 删除对应缓存数据  */
				manager.removeCached(from);
			}
		}
	}
	/**
	 * 更新记录主键(适用只有一个主键的表)
	 * @param alias
	 * @param bean
	 * @param to
	 * @since 3.30.0
	 */
	public static <B extends BaseRow>void updatePk(String alias, B bean,Object[] to) {
		if(null!= bean) {
			updatePk(alias,bean.fetchMetaData().tablename,bean.primaryValues(), to);
		}
	}
	/**
	 * 更新记录主键(适用只有一个主键的表)
	 * @param alias
	 * @param bean
	 * @param to
	 * @since 3.30.0
	 */
	public static <B extends BaseRow>void updatePk(String alias, B bean,Object to) {
		if(null!= bean) {
			updatePk(alias,bean.fetchMetaData().tablename,bean.primaryValue(), to);
		}
	}
}
